package com.swak.fx.support.ui;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.CompletableFuture;

import com.swak.entity.SuccessNewFail;
import com.swak.fx.support.Display;
import com.swak.fx.support.FxContexts;
import com.swak.fx.support.ui.skin.BaseLabelSkin;
import com.swak.reactivex.threads.OrderContext.OrderAbleContext;
import com.swak.storage.DownloadStorage;
import com.swak.utils.FileUtils;
import com.swak.utils.IOUtils;
import com.swak.utils.StringUtils;

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;

/**
 * UIImage.
 * 
 * @author DELL
 */
public class UIImage extends Label implements UILifecycle {

	// ================================================================================
	// Properties
	// ================================================================================
	private final String STYLE_CLASS = "ui-image";
	private ImageView image = null;

	public UIImage() {
		super();
		init();
	}

	@Override
	public void initializeParts() {
		getStyleClass().add(STYLE_CLASS);
	}

	@Override
	public void layoutParts() {
		if (this.image == null || this.getPrefWidth() <= 0 || this.getPrefHeight() <= 0) {
			return;
		}

		// 设置图标
		this.image.setPreserveRatio(true);
		this.image.setSmooth(true);
		this.image.setCache(true);
		Image img = this.image.getImage();

		// 希望展示的宽度和高度
		double imgWidth = this.getPrefWidth();
		double imgHeight = this.getPrefHeight();

		// 等比例图片且等比例显示
		if (imgWidth == imgHeight && img.getWidth() == img.getHeight()) {
			this.image.setFitHeight(imgHeight);
			this.image.setFitWidth(imgWidth);
			this.setGraphic(this.image);
			return;
		}

		// 宽图
		if (img.getWidth() > img.getHeight()) {
			double w = img.getWidth() / img.getHeight() * imgHeight;
			double x = (w - imgWidth) / 2;
			this.image.setX(x * -1);
			this.image.setFitHeight(imgHeight);
		}

		// 竖图
		else {
			double h = img.getHeight() / img.getWidth() * imgWidth;
			double y = (h - imgHeight) / 2;
			this.image.setY(y * -1);
			this.image.setFitWidth(imgWidth);
		}

		// 设置可显示的区域
		Rectangle innerRect = new Rectangle(0, 0, imgWidth, imgHeight);
		this.image.setClip(innerRect);

		// 裁剪
		SnapshotParameters params = new SnapshotParameters();
		params.setFill(javafx.scene.paint.Color.TRANSPARENT);
		this.setGraphic(new ImageView(this.image.snapshot(params, null)));
	}

	@Override
	public void setupValueChangedListeners() {
		this.nodeProperty().addListener((ob, o, n) -> {
			if (n instanceof Image) {
				image = new ImageView((Image) n);
			} else if (n instanceof String && StringUtils.startsWithAny((String) n, "http://", "https://")) {
				this.download((String) n);
				return;
			} else if (n instanceof String) {
				try {
					File file = new File((String) n);
					InputStream is = FileUtils.in(file);
					image = new ImageView(new Image(is));
					IOUtils.closeQuietly(is);
				} catch (Exception e) {
				}
			} else if (n instanceof File) {
				try {
					File file = (File) n;
					InputStream is = FileUtils.in(file);
					image = new ImageView(new Image(is));
					IOUtils.closeQuietly(is);
				} catch (Exception e) {
				}
			} else if (n instanceof URL) {
				try {
					InputStream is = ((URL) n).openStream();
					image = new ImageView(new Image(is));
					IOUtils.closeQuietly(is);
				} catch (Exception e) {
				}
			} else if (n instanceof ImageView) {
				image = (ImageView) n;
			} else if (n instanceof InputStream) {
				image = new ImageView(new Image((InputStream) n));
				IOUtils.closeQuietly((InputStream) n);
			} else if (n instanceof byte[]) {
				ByteArrayInputStream bis = new ByteArrayInputStream((byte[]) n);
				image = new ImageView(new Image(bis));
				// IOUtils.closeQuietly(bis);
			}
			this.layoutParts();
		});
		prefHeightProperty().addListener((ob, o, n) -> this.layoutParts());
		prefWidthProperty().addListener((ob, o, n) -> this.layoutParts());
	}

	private void download(String url) {
		ImageCache cache = new ImageCache(url);
		cache.successProperty.whenComplete((r, e) -> {
			if (r == SuccessNewFail.Success) {
				Display.runUI(() -> {
					this.image(cache.openStream());
				});
			}
		});
		cache.start();
	}

	// ================================================================================
	// Func
	// ================================================================================
	public UIImage Direct(URL url) {
		try {
			InputStream is = ((URL) url).openStream();
			image = new ImageView(new Image(is));
			IOUtils.closeQuietly(is);
		} catch (Exception e) {
		}
		this.layoutParts();
		return this;
	}

	// ================================================================================
	// Properties
	// ================================================================================
	private final ObjectProperty<Object> nodeProperty = new SimpleObjectProperty<Object>(this, "image");
	private ObjectProperty<Pos> posProperty = new SimpleObjectProperty<>(Pos.TOP_RIGHT);
	private ObjectProperty<Node> decorationProperty = new SimpleObjectProperty<>(this, "decoration");
	private DoubleProperty offsetXProperty = new SimpleDoubleProperty(0);
	private DoubleProperty offsetYProperty = new SimpleDoubleProperty(0);

	public ObjectProperty<Node> decorationProperty() {
		return decorationProperty;
	}

	public ObjectProperty<Pos> posProperty() {
		return posProperty;
	}

	public ObjectProperty<Object> nodeProperty() {
		return nodeProperty;
	}

	public DoubleProperty offsetXProperty() {
		return offsetXProperty;
	}

	public DoubleProperty offsetYProperty() {
		return offsetYProperty;
	}

	public UIImage image(URL url) {
		nodeProperty().set(url);
		return this;
	}

	public UIImage image(Image image) {
		nodeProperty().set(image);
		return this;
	}

	public UIImage image(InputStream image) {
		nodeProperty().set(image);
		return this;
	}

	public UIImage image(ImageView image) {
		nodeProperty().set(image);
		return this;
	}

	public UIImage image(String image) {
		nodeProperty().set(image);
		return this;
	}

	public UIImage round() {
		double round = this.prefWidth(-1) / 2;
		Circle circle = new Circle();
		circle.setRadius(round);
		circle.setCenterX(round);
		circle.setCenterY(round);
		this.setClip(circle);
		return this;
	}

	@Override
	protected Skin<?> createDefaultSkin() {
		return new UIImageSkin(this);
	}

	public class UIImageSkin extends BaseLabelSkin {
		final UIImage control;

		public UIImageSkin(UIImage control) {
			super(control);
			this.control = control;
			this.init();
		}

		@Override
		protected ObjectProperty<Pos> posProperty() {
			return control.posProperty();
		}

		@Override
		protected ObjectProperty<Node> decorationProperty() {
			return control.decorationProperty();
		}

		@Override
		protected DoubleProperty offsetXProperty() {
			return control.offsetXProperty();
		}

		@Override
		protected DoubleProperty offsetYProperty() {
			return control.offsetYProperty();
		}
	}

	// ================================================================================
	// Static
	// ================================================================================
	public static UIImage Image(String image) {
		return new UIImage().image(image);
	}

	public static UIImage Image(Image image) {
		return new UIImage().image(image);
	}

	public static UIImage Image(InputStream image) {
		return new UIImage().image(image);
	}

	public static UIImage Image(ImageView image) {
		return new UIImage().image(image);
	}

	// ================================================================================
	// Cache
	// ================================================================================
	public static class ImageCache {

		private static OrderAbleContext CONTEXT = null;

		private static void NewContext() {
			if (CONTEXT == null) {
				synchronized (ImageCache.class) {
					if (CONTEXT == null) {
						CONTEXT = FxContexts.New().getWorkerContext().newOrderAbleContext();
					}
				}
			}
		}

		private final DoubleProperty progressProperty;
		private final CompletableFuture<SuccessNewFail> successProperty;

		public CompletableFuture<SuccessNewFail> successProperty() {
			return successProperty;
		}

		private final String url;
		private File tempfile;

		public ImageCache(String url) {
			this.url = url;
			this.progressProperty = new SimpleDoubleProperty(0);
			this.successProperty = new CompletableFuture<SuccessNewFail>();
			this.tempfile = DownloadStorage.New().file(url);

			// 初始化任务队列
			NewContext();
		}

		public void clear() {
			if (tempfile != null) {
				tempfile.delete();
			}
		}

		public ImageCache start() {
			if (this.tempfile.exists()) {
				this.progressProperty.set(1);
				this.successProperty.complete(SuccessNewFail.Success);
			} else {
				CONTEXT.execute(this::download);
			}
			return this;
		}

		public InputStream openStream() {
			try {
				return new FileInputStream(tempfile);
			} catch (Exception e) {
				return null;
			}
		}

		private void download() {
			HttpURLConnection connection = null;
			InputStream read = null;
			OutputStream write = null;
			try {
				URL postUrl = new URL(this.url);
				connection = (HttpURLConnection) postUrl.openConnection();
				connection.setUseCaches(false);
				connection.setRequestProperty("User-Agent",
						" Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36");
				connection.setRequestProperty("Accept-Encoding", "identity");
				connection.setConnectTimeout(15000);
				connection.setReadTimeout(15000);
				connection.connect();
				if (200 == connection.getResponseCode()) {
					read = connection.getInputStream();
					write = new FileOutputStream(this.tempfile);
					long total = connection.getContentLength();
					byte[] buffer = new byte[10];
					int len = 0;
					int count = 0;
					while (-1 != (len = read.read(buffer))) {
						write.write(buffer, 0, len);
						count += len;
						double rate = ((count / (float) total) * 100);
						if (rate >= 1.0) {
							rate = 0.99;
						}
						this.progressProperty.set(rate);
					}
					this.successProperty.complete(SuccessNewFail.Success);
					this.progressProperty.set(1.0);
				} else {
					this.successProperty.complete(SuccessNewFail.Fail);
				}
			} catch (Exception e) {
				this.successProperty.complete(SuccessNewFail.Fail);
			} finally {
				IOUtils.closeQuietly(read);
				IOUtils.closeQuietly(write);
				if (connection != null) {
					try {
						connection.disconnect();
					} catch (Exception e2) {
					}
				}
			}
		}
	}
}
